/********************************************************************************************

PNGU Version : 0.1c

Coder : frontier

More info : http://frontier-dev.net

********************************************************************************************/

#include "PNGU.h"
#include "../png.h"
#ifdef PNGU_USE_WII_SD
#include "../../wiisd/tff.h"
#include "../../wiisd/sdio.h"
#endif

// Constants
#define PNGU_SOURCE_UNDEFINED		0
#define PNGU_SOURCE_BUFFER			1
#ifdef PNGU_USE_WII_SD
#define PNGU_SOURCE_WIISD			2
#define PNGU_MAX_PATH				2048
#endif

// Prototypes of helper functions
int pngu_decode (PNGU_u32 width, PNGU_u32 height, png_structpp png_ptr, png_infopp info_ptr, png_infopp end_info);
void pngu_read_data_from_buffer (png_structp png_ptr, png_bytep data, png_size_t length);
#ifdef PNGU_USE_WII_SD
void pngu_read_data_from_wiisd (png_structp png_ptr, png_bytep data, png_size_t length);
#endif

// Variables used to store PNGU internal state
int pnguinitialized = 0;
int pngusource = PNGU_SOURCE_UNDEFINED;
PNGU_u32 pngucursor = 0;
void *pngubuffer = NULL;
#ifdef PNGU_USE_WII_SD
char pngufilename[PNGU_MAX_PATH];
FATFS pngufrontfs;
FIL pngufp;
#endif


int PNGU_Init ()
{
	if (pnguinitialized)
		return PNGU_ALREADY_INITIALIZED;

	#ifdef PNGU_USE_WII_SD
		if(f_mount(0, &pngufrontfs) != FR_OK)
			return -1;
	#endif

	pnguinitialized = 1;

	return PNGU_OK;
}


void PNGU_Release ()
{
	if (!pnguinitialized)
		return;

	#ifdef PNGU_USE_WII_SD
		f_mount(0, NULL);
	#endif

	pnguinitialized = 0;
}


int PNGU_SelectFileFromBuffer (const void *buffer)
{
	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	if (!buffer)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return PNGU_INVALID_BUFFER;
	}

	pngubuffer = (void *) buffer;
	pngusource = PNGU_SOURCE_BUFFER;

	return PNGU_OK;
}


#ifdef PNGU_USE_WII_SD
int PNGU_SelectFileFromWiiSD (const char *filename)
{
	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	if (strlen(filename) >= PNGU_MAX_PATH)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return PNGU_PATH_TOO_LONG;
	}

	strcpy(pngufilename, filename);
	pngusource = PNGU_SOURCE_WIISD;

	return PNGU_OK;
}
#endif


int PNGU_GetImageProperties (PNGUPROP *fileproperties)
{
	png_byte magic[8];
	png_structp png_ptr;
	png_infop info_ptr;
	png_uint_32 width;
	png_uint_32 height;

	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	// Check if there is a file selected and if it is a valid .png
	if (pngusource == PNGU_SOURCE_UNDEFINED)
		return PNGU_NO_FILE_SELECTED;

	else if (pngusource == PNGU_SOURCE_BUFFER)
		memcpy (magic, pngubuffer, 8);

	#ifdef PNGU_USE_WII_SD
	else if (pngusource == PNGU_SOURCE_WIISD)
	{
		WORD bytes_read;
		FILINFO pngufinfo;

		// Retrieve file attributes
		if (f_stat (pngufilename, &pngufinfo) != FR_OK)
			return PNGU_CANT_READ_FILE;
		fileproperties->fileSize = (PNGU_u32) pngufinfo.fsize;
		fileproperties->fileAttrib = 0;
		if (pngufinfo.fattrib & AM_RDO)
			fileproperties->fileAttrib = fileproperties->fileAttrib | PNGU_FATTRIB_READ_ONLY;
		if (pngufinfo.fattrib & AM_HID)
			fileproperties->fileAttrib = fileproperties->fileAttrib | PNGU_FATTRIB_HIDDEN;
		if (pngufinfo.fattrib & AM_SYS)
			fileproperties->fileAttrib = fileproperties->fileAttrib | PNGU_FATTRIB_SYSTEM;

		// Load first 8 bytes into magic buffer
		if (f_open (&pngufp, pngufilename, FA_READ) != FR_OK)
			return PNGU_CANT_OPEN_FILE;
        if (f_read (&pngufp, magic, 8, &bytes_read) != FR_OK)
		{
			f_close (&pngufp);
			return PNGU_CANT_READ_FILE;
		}
	}
	#endif

	else
		return PNGU_LIB_ERROR;

	if (png_sig_cmp(magic, 0, 8) != 0)
	{
		#ifdef PNGU_USE_WII_SD
		if (pngusource == PNGU_SOURCE_WIISD)
			f_close (&pngufp);
		#endif
		return PNGU_FILE_NOT_PNG;
	}

	// Allocation of libpng structs
	png_ptr = png_create_read_struct
       (PNG_LIBPNG_VER_STRING, NULL,
        NULL, NULL);
    if (!(png_ptr))
	{
		#ifdef PNGU_USE_WII_SD
		if (pngusource == PNGU_SOURCE_WIISD)
			f_close (&pngufp);
		#endif
        return (PNGU_LIB_ERROR);
	}

    info_ptr = png_create_info_struct(png_ptr);
    if (!(info_ptr))
    {
		#ifdef PNGU_USE_WII_SD
		if (pngusource == PNGU_SOURCE_WIISD)
			f_close (&pngufp);
		#endif
        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
        return (PNGU_LIB_ERROR);
    }

	// Installation of our custom data provider function
	if (pngusource == PNGU_SOURCE_BUFFER)
	{
		pngucursor = 0;
		png_set_read_fn (png_ptr, NULL, pngu_read_data_from_buffer);
	}
	#ifdef PNGU_USE_WII_SD
	else if (pngusource == PNGU_SOURCE_WIISD)
	{
		png_set_sig_bytes (png_ptr, 8); // We have read 8 bytes already to check PNG authenticity
		png_set_read_fn (png_ptr, NULL, pngu_read_data_from_wiisd);
	}
	#endif

	// Query image properties
	// TODO: Read background and tranparent colors
	png_read_info (png_ptr, info_ptr);
	png_get_IHDR(png_ptr, info_ptr, &width, &height,
				(int *) &(fileproperties->imgBitDepth), 
				(int *) &(fileproperties->imgColorType),
				NULL, NULL, NULL);

	fileproperties->imgWidth = width;
	fileproperties->imgHeight = height;
	switch (fileproperties->imgColorType)
	{
		case PNG_COLOR_TYPE_GRAY:
			fileproperties->imgColorType = PNGU_COLOR_TYPE_GRAY;
			break;
		case PNG_COLOR_TYPE_GRAY_ALPHA:
			fileproperties->imgColorType = PNGU_COLOR_TYPE_GRAY_ALPHA;
			break;
		case PNG_COLOR_TYPE_PALETTE:
			fileproperties->imgColorType = PNGU_COLOR_TYPE_PALETTE;
			break;
		case PNG_COLOR_TYPE_RGB:
			fileproperties->imgColorType = PNGU_COLOR_TYPE_RGB;
			break;
		case PNG_COLOR_TYPE_RGB_ALPHA:
			fileproperties->imgColorType = PNGU_COLOR_TYPE_RGB_ALPHA;
			break;
		default:
			fileproperties->imgColorType = PNGU_COLOR_UNKNOWN;
			break;
	}

	// Free resources
	png_destroy_read_struct (&png_ptr, &info_ptr, (png_infopp)NULL);
	#ifdef PNGU_USE_WII_SD
	if (pngusource == PNGU_SOURCE_WIISD)
		f_close (&pngufp);
	#endif

	return PNGU_OK;
}


int PNGU_DecodeToYCbYCr (PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u32 stride)
{
	int result;
	PNGU_u32 x, y, buffWidth;
	png_bytep *row_pointers;
	png_structp png_ptr;
	png_infop info_ptr;
	png_infop end_info;

	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	// width needs to be divisible by two
	if (width % 2)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return PNGU_ODD_WIDTH;
	}

	// stride needs to be divisible by two
	if (stride % 2)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return PNGU_ODD_STRIDE;
	}

	result = pngu_decode (width, height, &png_ptr, &info_ptr, &end_info);
	if (result != PNGU_OK)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return result;
	}

	// Copy image to the output buffer
	row_pointers = png_get_rows(png_ptr, info_ptr);
	buffWidth = (width + stride) / 2;
	for (y = 0; y < height; y++)
		for (x = 0; x < (width / 2); x++)
			((PNGU_u32 *)buffer)[y*buffWidth+x] = PNGU_RGB8_TO_YCbYCr (*(row_pointers[y]+x*6), *(row_pointers[y]+x*6+1), *(row_pointers[y]+x*6+2),
															*(row_pointers[y]+x*6+3), *(row_pointers[y]+x*6+4), *(row_pointers[y]+x*6+5));
	
	// Deallocation of libpng structs
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	// Succes
	pngusource = PNGU_SOURCE_UNDEFINED;
	return PNGU_OK;
}


int PNGU_DecodeToRGBA8 (PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u8 default_alpha)
{
	int result;
	PNGU_u32 x, y;
	png_bytep *row_pointers;
	png_structp png_ptr;
	png_infop info_ptr;
	png_infop end_info;

	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	result = pngu_decode (width, height, &png_ptr, &info_ptr, &end_info);
	if (result != PNGU_OK)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return result;
	}

	// Copy image to the output buffer
	row_pointers = png_get_rows(png_ptr, info_ptr);
	for (y = 0; y < height; y++)
		for (x = 0; x < width; x++)
			((PNGU_u32 *)buffer)[y*width+x] = 
				(((PNGU_u32) *(row_pointers[y]+x*6)) << 24) | (((PNGU_u32) *(row_pointers[y]+x*6+1)) << 16) | 
				(((PNGU_u32) *(row_pointers[y]+x*6+2)) << 8) | ((PNGU_u32) default_alpha);
	
	// Deallocation of libpng structs
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	// Succes
	pngusource = PNGU_SOURCE_UNDEFINED;
	return PNGU_OK;
}


int PNGU_DecodeTo4x4RGB565 (PNGU_u32 width, PNGU_u32 height, void *buffer)
{
	int result;
	PNGU_u32 x, y, qwidth, qheight;
	png_bytep *row_pointers;
	png_structp png_ptr;
	png_infop info_ptr;
	png_infop end_info;

	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	// width and height need to be divisible by four
	if ((width % 4) || (height % 4))
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return PNGU_INVALID_WIDTH_OR_HEIGHT;
	}

	result = pngu_decode (width, height, &png_ptr, &info_ptr, &end_info);
	if (result != PNGU_OK)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return result;
	}

	// Copy image to the output buffer
	row_pointers = png_get_rows(png_ptr, info_ptr);
	qwidth = width / 4;
	qheight = height / 4;

	for (y = 0; y < qheight; y++)
		for (x = 0; x < qwidth; x++)
		{
			int blockbase = (y * qwidth + x) * 4;

			PNGU_u64 field64 = *((PNGU_u64 *)(row_pointers[y*4]+x*12));
			PNGU_u64 field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase] = 
				(((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | 
				(((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | 
				(((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) |
				(((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3)));

			field64 = *((PNGU_u64 *)(row_pointers[y*4+1]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+1]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+1] = 
				(((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | 
				(((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | 
				(((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) |
				(((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3)));

			field64 = *((PNGU_u64 *)(row_pointers[y*4+2]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+2]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+2] = 
				(((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | 
				(((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | 
				(((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) |
				(((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3)));

			field64 = *((PNGU_u64 *)(row_pointers[y*4+3]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+3]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+3] = 
				(((field64 & 0xF800000000000000ULL) | ((field64 & 0xFC000000000000ULL) << 3) | ((field64 & 0xF80000000000ULL) << 5)) | 
				(((field64 & 0xF800000000ULL) << 8) | ((field64 & 0xFC000000ULL) << 11) | ((field64 & 0xF80000ULL) << 13)) | 
				(((field64 & 0xF800ULL) << 16) | ((field64 & 0xFCULL) << 19) | ((field32 & 0xF8000000ULL) >> 11)) |
				(((field32 & 0xF80000ULL) >> 8) | ((field32 & 0xFC00ULL) >> 5) | ((field32 & 0xF8ULL) >> 3)));
		}
	
	// Deallocation of libpng structs
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	// Succes
	pngusource = PNGU_SOURCE_UNDEFINED;
	return PNGU_OK;
}


int PNGU_DecodeTo4x4RGB5A3 (PNGU_u32 width, PNGU_u32 height, void *buffer)
{
	int result;
	PNGU_u32 x, y, qwidth, qheight;
	PNGU_u64 alphaMask;
	png_bytep *row_pointers;
	png_structp png_ptr;
	png_infop info_ptr;
	png_infop end_info;

	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	// width and height need to be divisible by four
	if ((width % 4) || (height % 4))
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return PNGU_INVALID_WIDTH_OR_HEIGHT;
	}

	result = pngu_decode (width, height, &png_ptr, &info_ptr, &end_info);
	if (result != PNGU_OK)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return result;
	}

	// Copy image to the output buffer
	row_pointers = png_get_rows(png_ptr, info_ptr);
	qwidth = width / 4;
	qheight = height / 4;
	alphaMask = 0x8000800080008000ULL;

	for (y = 0; y < qheight; y++)
		for (x = 0; x < qwidth; x++)
		{
			int blockbase = (y * qwidth + x) * 4;

			PNGU_u64 field64 = *((PNGU_u64 *)(row_pointers[y*4]+x*12));
			PNGU_u64 field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase] = 
				 alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) |
				((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) |
				((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | 
				((field32 & 0xF8000000ULL) >> 11) |	((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3);

			field64 = *((PNGU_u64 *)(row_pointers[y*4+1]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+1]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+1] = 
				alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) |
				((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) |
				((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | 
				((field32 & 0xF8000000ULL) >> 11) |	((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3);

			field64 = *((PNGU_u64 *)(row_pointers[y*4+2]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+2]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+2] = 
				alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) |
				((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) |
				((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | 
				((field32 & 0xF8000000ULL) >> 11) |	((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3);

			field64 = *((PNGU_u64 *)(row_pointers[y*4+3]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+3]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+3] = 
				alphaMask | ((field64 & 0xF800000000000000ULL) >> 1) | ((field64 & 0xF8000000000000ULL) << 2) |
				((field64 & 0xF80000000000ULL) << 5) | ((field64 & 0xF800000000ULL) << 7) | ((field64 & 0xF8000000ULL) << 10) |
				((field64 & 0xF80000ULL) << 13) | ((field64 & 0xF800ULL) << 15) | ((field64 & 0xF8ULL) << 18) | 
				((field32 & 0xF8000000ULL) >> 11) |	((field32 & 0xF80000ULL) >> 9) | ((field32 & 0xF800ULL) >> 6) | ((field32 & 0xF8ULL) >> 3);
		}
	
	// Deallocation of libpng structs
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	// Succes
	pngusource = PNGU_SOURCE_UNDEFINED;
	return PNGU_OK;
}


int PNGU_DecodeTo4x4RGBA8 (PNGU_u32 width, PNGU_u32 height, void *buffer, PNGU_u8 default_alpha)
{
	int result;
	PNGU_u32 x, y, qwidth, qheight;
	PNGU_u64 alphaMask;
	png_bytep *row_pointers;
	png_structp png_ptr;
	png_infop info_ptr;
	png_infop end_info;

	if (!pnguinitialized)
		return PNGU_NOT_INITIALIZED;

	// width and height need to be divisible by four
	if ((width % 4) || (height % 4))
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return PNGU_INVALID_WIDTH_OR_HEIGHT;
	}

	result = pngu_decode (width, height, &png_ptr, &info_ptr, &end_info);
	if (result != PNGU_OK)
	{
		pngusource = PNGU_SOURCE_UNDEFINED;
		return result;
	}

	// Copy image to the output buffer
	row_pointers = png_get_rows(png_ptr, info_ptr);
	qwidth = width / 4;
	qheight = height / 4;
	alphaMask = (((PNGU_u64)default_alpha) << 56) | (((PNGU_u64)default_alpha) << 40) |
				(((PNGU_u64)default_alpha) << 24) | (((PNGU_u64)default_alpha) << 8);

	for (y = 0; y < qheight; y++)
		for (x = 0; x < qwidth; x++)
		{
			int blockbase = (y * qwidth + x) * 8;

			PNGU_u64 field64 = *((PNGU_u64 *)(row_pointers[y*4]+x*12));
			PNGU_u64 field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase] = 
				(((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | 
				((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask);
			((PNGU_u64 *) buffer)[blockbase+4] =
				(((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) |
				((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL));

			field64 = *((PNGU_u64 *)(row_pointers[y*4+1]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+1]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+1] = 
				(((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | 
				((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask);
			((PNGU_u64 *) buffer)[blockbase+5] =
				(((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) |
				((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL));

			field64 = *((PNGU_u64 *)(row_pointers[y*4+2]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+2]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+2] = 
				(((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | 
				((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask);
			((PNGU_u64 *) buffer)[blockbase+6] =
				(((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) |
				((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL));

			field64 = *((PNGU_u64 *)(row_pointers[y*4+3]+x*12));
			field32 = (PNGU_u64) *((PNGU_u32 *)(row_pointers[y*4+3]+x*12+8));
			((PNGU_u64 *) buffer)[blockbase+3] = 
				(((field64 & 0xFF00000000000000ULL) >> 8) | (field64 & 0xFF00000000ULL) | 
				((field64 & 0xFF00ULL) << 8) | ((field32 & 0xFF0000ULL) >> 16) | alphaMask);
			((PNGU_u64 *) buffer)[blockbase+7] =
				(((field64 & 0xFFFF0000000000ULL) << 8) | ((field64 & 0xFFFF0000ULL) << 16) |
				((field64 & 0xFFULL) << 24) | ((field32 & 0xFF000000ULL) >> 8) | (field32 & 0xFFFFULL));
		}
	
	// Deallocation of libpng structs
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	// Succes
	pngusource = PNGU_SOURCE_UNDEFINED;
	return PNGU_OK;
}


int pngu_decode (PNGU_u32 width, PNGU_u32 height, png_structpp png_ptr, png_infopp info_ptr, png_infopp end_info)
{
	png_byte magic[8];

	// ¿Is there any file selected?
	if (pngusource == PNGU_SOURCE_UNDEFINED)
		return PNGU_NO_FILE_SELECTED;

	// ¿Do we have a valid PNG file?
	if (pngusource == PNGU_SOURCE_BUFFER)
		memcpy (magic, pngubuffer, 8);

	#ifdef PNGU_USE_WII_SD
	else if (pngusource == PNGU_SOURCE_WIISD)
	{
		WORD bytes_read;
		if (f_open (&pngufp, pngufilename, FA_READ) != FR_OK)
			return PNGU_CANT_OPEN_FILE;
        if (f_read (&pngufp, magic, 8, &bytes_read) != FR_OK)
		{
			f_close (&pngufp);
			return PNGU_CANT_READ_FILE;
		}
	}
	#endif

	else
		return PNGU_LIB_ERROR;

	if (png_sig_cmp(magic, 0, 8) != 0)
	{
		#ifdef PNGU_USE_WII_SD
		if (pngusource == PNGU_SOURCE_WIISD)
			f_close (&pngufp);
		#endif
		return PNGU_FILE_NOT_PNG;
	}

	// Allocation of libpng structs
	*png_ptr = png_create_read_struct
       (PNG_LIBPNG_VER_STRING, NULL,
        NULL, NULL);
    if (!(*png_ptr))
	{
		#ifdef PNGU_USE_WII_SD
		if (pngusource == PNGU_SOURCE_WIISD)
			f_close (&pngufp);
		#endif
        return (PNGU_LIB_ERROR);
	}

    *info_ptr = png_create_info_struct(*png_ptr);
    if (!(*info_ptr))
    {
		#ifdef PNGU_USE_WII_SD
		if (pngusource == PNGU_SOURCE_WIISD)
			f_close (&pngufp);
		#endif
        png_destroy_read_struct(png_ptr, (png_infopp)NULL, (png_infopp)NULL);
        return (PNGU_LIB_ERROR);
    }

    *end_info = png_create_info_struct(*png_ptr);
    if (!(*end_info))
    {
		#ifdef PNGU_USE_WII_SD
		if (pngusource == PNGU_SOURCE_WIISD)
			f_close (&pngufp);
		#endif
        png_destroy_read_struct(png_ptr, info_ptr, (png_infopp)NULL);
        return (PNGU_LIB_ERROR);
    }

	// Installation of our custom data provider function
	if (pngusource == PNGU_SOURCE_BUFFER)
	{
		pngucursor = 0;
		png_set_read_fn (*png_ptr, NULL, pngu_read_data_from_buffer);
	}
	#ifdef PNGU_USE_WII_SD
	else if (pngusource == PNGU_SOURCE_WIISD)
	{
		png_set_sig_bytes (*png_ptr, 8); // We have read 8 bytes already to check PNG authenticity
		png_set_read_fn (*png_ptr, NULL, pngu_read_data_from_wiisd);
	}
	#endif

	// Decode image into memory
	png_read_png (*png_ptr, *info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_PACKING, NULL);

	// Free resources of custom data provider functions
	#ifdef PNGU_USE_WII_SD
	if (pngusource == PNGU_SOURCE_WIISD)
		f_close (&pngufp);
	#endif

	// Succes
	return PNGU_OK;
}


void pngu_read_data_from_buffer (png_structp png_ptr, png_bytep data, png_size_t length)
{
	memcpy (data, pngubuffer+pngucursor, length);
	pngucursor += length;
}


#ifdef PNGU_USE_WII_SD
void pngu_read_data_from_wiisd (png_structp png_ptr, png_bytep data, png_size_t length)
{
	WORD bytes_read;
	PNGU_u32 bytes_total = 0;

	if (length <= 0x200)
		f_read (&pngufp, data, length, &bytes_read);
	else
	{
		while ((bytes_total + 0x200) <= length) // We trust in the fact tha libpng will not try to read more bytes than the file length
		{
			f_read (&pngufp, data + bytes_total, 0x200, &bytes_read);
			bytes_total += bytes_read;
		}
		if (bytes_total < length)
			f_read (&pngufp, data + bytes_total, length - bytes_total, &bytes_read);
	}
}
#endif


// This function is taken from a libogc example
PNGU_u32 PNGU_RGB8_TO_YCbYCr (PNGU_u8 r1, PNGU_u8 g1, PNGU_u8 b1, PNGU_u8 r2, PNGU_u8 g2, PNGU_u8 b2)
{
  int y1, cb1, cr1, y2, cb2, cr2, cb, cr;

  y1 = (299 * r1 + 587 * g1 + 114 * b1) / 1000;
  cb1 = (-16874 * r1 - 33126 * g1 + 50000 * b1 + 12800000) / 100000;
  cr1 = (50000 * r1 - 41869 * g1 - 8131 * b1 + 12800000) / 100000;
 
  y2 = (299 * r2 + 587 * g2 + 114 * b2) / 1000;
  cb2 = (-16874 * r2 - 33126 * g2 + 50000 * b2 + 12800000) / 100000;
  cr2 = (50000 * r2 - 41869 * g2 - 8131 * b2 + 12800000) / 100000;
 
  cb = (cb1 + cb2) >> 1;
  cr = (cr1 + cr2) >> 1;
 
  return (PNGU_u32) ((y1 << 24) | (cb << 16) | (y2 << 8) | cr);
}

